package com.xgit.iov.common.handler;

import com.baomidou.mybatisplus.core.parser.AbstractJsqlParser;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.xgit.iov.common.core.util.SecurityUtils;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;

import java.util.List;

/**
 * @Author: william
 * @Date: 2020/7/13 8:42
 */

@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class MyTenantSqlParser extends AbstractJsqlParser {

	private MyTenantHandler tenantHandler;

	/**
	 * select 语句处理
	 */
	@Override
	public void processSelectBody(SelectBody selectBody) {
		if (tenantHandler.doUserFilter(SecurityUtils.getUser())){
			//对系统用户不过滤
			return;
		}
		if (selectBody instanceof PlainSelect) {
			processPlainSelect((PlainSelect) selectBody);
		} else if (selectBody instanceof WithItem) {
			WithItem withItem = (WithItem) selectBody;
			if (withItem.getSelectBody() != null) {
				processSelectBody(withItem.getSelectBody());
			}
		} else {
			SetOperationList operationList = (SetOperationList) selectBody;
			if (operationList.getSelects() != null && operationList.getSelects().size() > 0) {
				operationList.getSelects().forEach(this::processSelectBody);
			}
		}
	}

	/**
	 * <p>
	 * insert 语句处理
	 * </p>
	 */
	@Override
	public void processInsert(Insert insert) {
		if (tenantHandler.doTableFilter(insert.getTable().getName())) {
			// 过滤退出执行
			return;
		}
		if (tenantHandler.doUserFilter(SecurityUtils.getUser())){
			//对系统用户不过滤
			return;
		}
		if(insert.getColumns().toString().contains(this.getTenantHandler().getTenantIdColumn())){
			return;
		}

		insert.getColumns().add(new Column(this.tenantHandler.getTenantIdColumn()));
		if (insert.getSelect() != null) {
			this.processPlainSelect((PlainSelect)insert.getSelect().getSelectBody(), true);
		} else {
			if (insert.getItemsList() == null) {
				throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId", new Object[0]);
			}

			ItemsList itemsList = insert.getItemsList();
			if (itemsList instanceof MultiExpressionList) {
				((MultiExpressionList)itemsList).getExprList().forEach((el) -> {
					el.getExpressions().add(this.tenantHandler.getTenantId(false));
				});
			} else {
				((ExpressionList)insert.getItemsList()).getExpressions().add(this.tenantHandler.getTenantId(false));
			}
		}
	}

	/**
	 * <p>
	 * update 语句处理
	 * </p>
	 */
	@Override
	public void processUpdate(Update update) {
		if (tenantHandler.doUserFilter(SecurityUtils.getUser())){
			//对系统用户不过滤
			return;
		}
		Table table = update.getTable();
		if (tenantHandler.doTableFilter(table.getName())) {
			// 过滤退出执行
			return;
		}
		update.setWhere(this.andExpression(table, update.getWhere()));
	}

	/**
	 * <p>
	 * delete 语句处理
	 * </p>
	 */
	@Override
	public void processDelete(Delete delete) {
		if (tenantHandler.doUserFilter(SecurityUtils.getUser())){
			//对系统用户不过滤
			return;
		}
		if (tenantHandler.doTableFilter(delete.getTable().getName())) {
			// 过滤退出执行
			return;
		}
		delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere()));
	}

	/**
	 * <p>
	 * delete update 语句 where 处理
	 * </p>
	 */
	protected BinaryExpression andExpression(Table table, Expression where) {
		//获得where条件表达式
		EqualsTo equalsTo = new EqualsTo();
		equalsTo.setLeftExpression(this.getAliasColumn(table));
		equalsTo.setRightExpression(this.tenantHandler.getTenantId(true));
		if (null != where) {
			if (where instanceof OrExpression) {
				return new AndExpression(equalsTo, new Parenthesis(where));
			} else {
				return new AndExpression(equalsTo, where);
			}
		}
		return equalsTo;
	}

	/**
	 * <p>
	 * 处理 PlainSelect
	 * </p>
	 */
	protected void processPlainSelect(PlainSelect plainSelect) {
		processPlainSelect(plainSelect, false);
	}

	/**
	 * <p>
	 * 处理 PlainSelect
	 * </p>
	 *
	 * @param plainSelect
	 * @param addColumn   是否添加租户列,insert into select语句中需要
	 */
	protected void processPlainSelect(PlainSelect plainSelect, boolean addColumn) {
		FromItem fromItem = plainSelect.getFromItem();
		if (fromItem instanceof Table) {
			Table fromTable = (Table) fromItem;
			if (tenantHandler.doTableFilter(fromTable.getName())) {
				// 过滤退出执行
				return;
			}
			plainSelect.setWhere(builderExpression(plainSelect.getWhere(), fromTable));
			if (addColumn) {
				plainSelect.getSelectItems().add(new SelectExpressionItem(new Column(tenantHandler.getTenantIdColumn())));
			}
		} else {
			processFromItem(fromItem);
		}
		List<Join> joins = plainSelect.getJoins();
		if (joins != null && joins.size() > 0) {
			joins.forEach(j -> {
				processJoin(j);
				processFromItem(j.getRightItem());
			});
		}
	}

	/**
	 * 处理子查询等
	 */
	protected void processFromItem(FromItem fromItem) {
		if (fromItem instanceof SubJoin) {
			SubJoin subJoin = (SubJoin) fromItem;
			if (subJoin.getJoinList() != null) {
				subJoin.getJoinList().forEach(this::processJoin);
			}
			if (subJoin.getLeft() != null) {
				processFromItem(subJoin.getLeft());
			}
		} else if (fromItem instanceof SubSelect) {
			SubSelect subSelect = (SubSelect) fromItem;
			if (subSelect.getSelectBody() != null) {
				processSelectBody(subSelect.getSelectBody());
			}
		} else if (fromItem instanceof ValuesList) {
			logger.debug("Perform a subquery, if you do not give us feedback");
		} else if (fromItem instanceof LateralSubSelect) {
			LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
			if (lateralSubSelect.getSubSelect() != null) {
				SubSelect subSelect = lateralSubSelect.getSubSelect();
				if (subSelect.getSelectBody() != null) {
					processSelectBody(subSelect.getSelectBody());
				}
			}
		}
	}

	/**
	 * 处理联接语句
	 */
	protected void processJoin(Join join) {
		if (join.getRightItem() instanceof Table) {
			Table fromTable = (Table) join.getRightItem();
			if (this.tenantHandler.doTableFilter(fromTable.getName())) {
				// 过滤退出执行
				return;
			}
			join.setOnExpression(builderExpression(join.getOnExpression(), fromTable));
		}
	}

	/**
	 * 处理条件
	 */
	protected Expression builderExpression(Expression expression, Table table) {
		//生成字段名
		EqualsTo equalsTo = new EqualsTo();
		equalsTo.setLeftExpression(this.getAliasColumn(table));
		equalsTo.setRightExpression(tenantHandler.getTenantId(false));
		//加入判断防止条件为空时生成 "and null" 导致查询结果为空
		if (expression == null) {
			return equalsTo;
		} else {
			if (expression instanceof BinaryExpression) {
				BinaryExpression binaryExpression = (BinaryExpression) expression;
				if (binaryExpression.getLeftExpression() instanceof FromItem) {
					processFromItem((FromItem) binaryExpression.getLeftExpression());
				}
				if (binaryExpression.getRightExpression() instanceof FromItem) {
					processFromItem((FromItem) binaryExpression.getRightExpression());
				}
			}
			if (expression instanceof OrExpression) {
				return new AndExpression(equalsTo, new Parenthesis(expression));
			} else {
				return new AndExpression(equalsTo, expression);
			}
		}
	}

	/**
	 * <p>
	 * 租户字段别名设置<br>
	 * tableName.tenantId 或 tableAlias.tenantId
	 * </p>
	 *
	 * @param table 表对象
	 * @return 字段
	 */
	protected Column getAliasColumn(Table table) {
		StringBuilder column = new StringBuilder();
		if (null == table.getAlias()) {
			column.append(table.getName());
		} else {
			column.append(table.getAlias().getName());
		}
		column.append(StringPool.DOT);
		column.append(tenantHandler.getTenantIdColumn());
		return new Column(column.toString());
	}

}
